<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AQS抽象的队列同步器 | Kylin</title><meta name="keywords" content="JUC,AQS"><meta name="author" content="Kylin"><meta name="copyright" content="Kylin"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="概念是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石，通过内置的FIFO队列来完成资源获取线程的排队工作，并通过一个volatile的int类型变量state表示持有锁的状态。 抽象的队列式的同步器，AQS定义了一套多线程访问共享资源的同步器框架，许多同步类实现都依赖于它，如常用的ReentrantLock、Semaphore、CountDownLatch、ReentrantR">
<meta property="og:type" content="article">
<meta property="og:title" content="AQS抽象的队列同步器">
<meta property="og:url" content="https://www.codekylin.cn/59732.html">
<meta property="og:site_name" content="Kylin">
<meta property="og:description" content="概念是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石，通过内置的FIFO队列来完成资源获取线程的排队工作，并通过一个volatile的int类型变量state表示持有锁的状态。 抽象的队列式的同步器，AQS定义了一套多线程访问共享资源的同步器框架，许多同步类实现都依赖于它，如常用的ReentrantLock、Semaphore、CountDownLatch、ReentrantR">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://qiniu.codekylin.cn/github/img/img/博客封面14.png">
<meta property="article:published_time" content="2021-12-26T01:09:03.000Z">
<meta property="article:modified_time" content="2022-07-12T11:42:50.040Z">
<meta property="article:author" content="Kylin">
<meta property="article:tag" content="JUC">
<meta property="article:tag" content="AQS">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://qiniu.codekylin.cn/github/img/img/博客封面14.png"><link rel="shortcut icon" href="https://qiniu.codekylin.cn/img/20200807181548.png"><link rel="canonical" href="https://www.codekylin.cn/59732"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""/><link rel="preconnect" href="//hm.baidu.com"/><link rel="preconnect" href="//fonts.googleapis.com" crossorigin=""/><meta name="google-site-verification" content="gzeyWstt6NoTZKh7YFYNLNziL8HIZ8YH2Ug7xTDX5-Y"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><script>var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?f76c34daefe747deee7c7be3ead2ba80";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script><script async="async" src="https://www.googletagmanager.com/gtag/js?id=UA-159334016-1"></script><script>window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-159334016-1');
</script><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium+Web" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"简"},
  noticeOutdate: {"limitDay":90,"position":"top","messagePrev":"自上次更新以来已经","messageNext":"天，文章的内容可能已过时或存在差异。"},
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: {"limitCount":50000,"languages":{"author":"作者: Kylin","link":"链接: ","source":"来源: Kylin","info":"著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。"}},
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#FF0000","bgDark":"#2d3035","position":"bottom-left"},
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = { 
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-07-12 19:42:50'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
          const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches
          const isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches
          const hasNoSupport = !isDarkMode && !isLightMode && !isNotSpecified

          if (t === undefined) {
            if (isLightMode) activateLightMode()
            else if (isDarkMode) activateDarkMode()
            else if (isNotSpecified || hasNoSupport) {
              const now = new Date()
              const hour = now.getHours()
              const isNight = hour <= 6 || hour >= 18
              isNight ? activateDarkMode() : activateLightMode()
            }
            window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
              if (saveToLocal.get('theme') === undefined) {
                e.matches ? activateDarkMode() : activateLightMode()
              }
            })
          } else if (t === 'light') activateLightMode()
          else activateDarkMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const fontSizeVal = saveToLocal.get('global-font-size')
    if (fontSizeVal !== undefined) {
      document.documentElement.style.setProperty('--global-font-size', fontSizeVal + 'px')
    }
    })(window)</script><link rel="stylesheet" href="https://qiniu.codekylin.cn/github/img/img/custom.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_1993646_z05rabxf05h.css"><link rel="stylesheet" href="https://qiniu.codekylin.cn/github/img/img/icon.css"><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="Kylin" type="application/atom+xml">
</head><body><div id="web_bg"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="author-avatar"><img class="avatar-img" data-lazy-src="https://qiniu.codekylin.cn/img/20200807181526.jpg" onerror="onerror=null;src='https://qiniu.codekylin.cn/github/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/archives/"><div class="headline">文章</div><div class="length-num">362</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/tags/"><div class="headline">标签</div><div class="length-num">427</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/categories/"><div class="headline">分类</div><div class="length-num">101</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://qiniu.codekylin.cn/github/img/img/博客封面14.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">Kylin</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">AQS抽象的队列同步器</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2021-12-26T01:09:03.000Z" title="发表于 2021-12-26 09:09:03">2021-12-26</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-07-12T11:42:50.040Z" title="更新于 2022-07-12 19:42:50">2022-07-12</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java/">Java</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java/JUC/">JUC</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">7.1k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>28分钟</span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石，通过内置的<strong>FIFO队列</strong>来完成资源获取线程的排队工作，并通过一个volatile的int类型变量<strong>state</strong>表示持有锁的状态。</p>
<p>抽象的队列式的同步器，AQS定义了一套多线程访问共享资源的同步器框架，许多同步类实现都依赖于它，如常用的ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock…</p>
<blockquote>
<p>锁，面向锁的使用者</p>
</blockquote>
<p>定义了程序员和锁交互的使用层API，隐藏了实现细节，调用即可。</p>
<blockquote>
<p>同步器，面向锁的实现者</p>
</blockquote>
<p>也就是通过同步器，我们可以实现各种不同的锁。</p>
<blockquote>
<p>为什么需要AQS</p>
</blockquote>
<p>加锁会导致阻塞，有阻塞就需要排队，实现排队必然需要某种形式的队列来进行管理。</p>
<p>抢到资源的线程直接使用处理业务逻辑，抢不到资源的必然涉及一种<strong>排队等候机制</strong>。抢占资源失败的线程继续去等待（类似银行业务办理窗口都满了，暂时没有受理窗口的顾客只能去候客区排队等候），但等候线程仍然保留获取锁的可能且获取锁流程仍在继续（候客区的顾客也在等着叫号，轮到了再去受理窗口办理业务）</p>
<p>既然说到了排队等候机制，那么就一定会有某种队列形成，这样的队列是什么数据结构呢?</p>
<p>如果共享资源被占用，就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的，将暂时获取不到锁的线程加入到队列中，这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的节点（Node），通过CAS，自旋以及LockSupport.park()的方式，维护state变量的状态，使并发达到同步的控制效果。</p>
<p>CLH：Craig、Landin and Hagersten队列，是单向链表，<strong>AQS中的队列是CLH变体的虚拟双向队列（FIFO）</strong>，AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/7132e4cef44c26f62835b197b239147b18062.png" alt="img"></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226112141932.png" alt="image-20211226112141932"></p>
<p>AQS</p>
<ul>
<li>同步状态State成员变量</li>
<li>CLH变体的虚拟双向队列</li>
</ul>
<p>State</p>
<ul>
<li>0就是没人，自由状态可以办理。</li>
<li>大于等于1，有人占用窗口，需要排队</li>
</ul>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226113406835.png" alt="image-20211226113406835"></p>
<p>总结</p>
<ol>
<li>锁会存在阻塞，有阻塞就需要排队，实现排队必然需要队列</li>
<li>AQS：state变量+CLH变种的双向队列</li>
</ol>
<h3 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h3><p>首先，我们通过下面的架构图来整体了解一下AQS框架：</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/82077ccf14127a87b77cefd1ccf562d3253591.png" alt="img"></p>
<ul>
<li>上图中有颜色的为Method，无颜色的为Attribution。</li>
<li>总的来说，AQS框架共分为五层，自上而下由浅入深，从AQS对外暴露的API到底层基础数据。</li>
<li>当有自定义同步器接入时，只需重写第一层所需要的部分方法即可，不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时，先经过第一层的API进入AQS内部方法，然后经过第二层进行锁的获取，接着对于获取锁失败的流程，进入第三层和第四层的等待队列处理，而这些处理方式均依赖于第五层的基础数据提供层。</li>
</ul>
<h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p>先来看下AQS中最基本的数据结构——Node，Node即为上面CLH变体队列中的节点。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/960271cf2b5c8a185eed23e98b72c75538637.png" alt="img"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </span>&#123;</span><br><span class="line">        <span class="comment">/** Marker to indicate a node is waiting in shared mode */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> Node SHARED = <span class="keyword">new</span> Node();</span><br><span class="line">        <span class="comment">/** Marker to indicate a node is waiting in exclusive mode */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> Node EXCLUSIVE = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/** waitStatus value to indicate thread has cancelled */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> CANCELLED =  <span class="number">1</span>;</span><br><span class="line">        <span class="comment">/** waitStatus value to indicate successor&#x27;s thread needs unparking */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SIGNAL    = -<span class="number">1</span>;</span><br><span class="line">        <span class="comment">/** waitStatus value to indicate thread is waiting on condition */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> CONDITION = -<span class="number">2</span>;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * waitStatus value to indicate the next acquireShared should</span></span><br><span class="line"><span class="comment">         * unconditionally propagate</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PROPAGATE = -<span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Status field, taking on only the values:</span></span><br><span class="line"><span class="comment">         *   SIGNAL:     The successor of this node is (or will soon be)</span></span><br><span class="line"><span class="comment">         *               blocked (via park), so the current node must</span></span><br><span class="line"><span class="comment">         *               unpark its successor when it releases or</span></span><br><span class="line"><span class="comment">         *               cancels. To avoid races, acquire methods must</span></span><br><span class="line"><span class="comment">         *               first indicate they need a signal,</span></span><br><span class="line"><span class="comment">         *               then retry the atomic acquire, and then,</span></span><br><span class="line"><span class="comment">         *               on failure, block.</span></span><br><span class="line"><span class="comment">         *   CANCELLED:  This node is cancelled due to timeout or interrupt.</span></span><br><span class="line"><span class="comment">         *               Nodes never leave this state. In particular,</span></span><br><span class="line"><span class="comment">         *               a thread with cancelled node never again blocks.</span></span><br><span class="line"><span class="comment">         *   CONDITION:  This node is currently on a condition queue.</span></span><br><span class="line"><span class="comment">         *               It will not be used as a sync queue node</span></span><br><span class="line"><span class="comment">         *               until transferred, at which time the status</span></span><br><span class="line"><span class="comment">         *               will be set to 0. (Use of this value here has</span></span><br><span class="line"><span class="comment">         *               nothing to do with the other uses of the</span></span><br><span class="line"><span class="comment">         *               field, but simplifies mechanics.)</span></span><br><span class="line"><span class="comment">         *   PROPAGATE:  A releaseShared should be propagated to other</span></span><br><span class="line"><span class="comment">         *               nodes. This is set (for head node only) in</span></span><br><span class="line"><span class="comment">         *               doReleaseShared to ensure propagation</span></span><br><span class="line"><span class="comment">         *               continues, even if other operations have</span></span><br><span class="line"><span class="comment">         *               since intervened.</span></span><br><span class="line"><span class="comment">         *   0:          None of the above</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * The values are arranged numerically to simplify use.</span></span><br><span class="line"><span class="comment">         * Non-negative values mean that a node doesn&#x27;t need to</span></span><br><span class="line"><span class="comment">         * signal. So, most code doesn&#x27;t need to check for particular</span></span><br><span class="line"><span class="comment">         * values, just for sign.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * The field is initialized to 0 for normal sync nodes, and</span></span><br><span class="line"><span class="comment">         * CONDITION for condition nodes.  It is modified using CAS</span></span><br><span class="line"><span class="comment">         * (or when possible, unconditional volatile writes).</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">volatile</span> <span class="keyword">int</span> waitStatus;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Link to predecessor node that current node/thread relies on</span></span><br><span class="line"><span class="comment">         * for checking waitStatus. Assigned during enqueuing, and nulled</span></span><br><span class="line"><span class="comment">         * out (for sake of GC) only upon dequeuing.  Also, upon</span></span><br><span class="line"><span class="comment">         * cancellation of a predecessor, we short-circuit while</span></span><br><span class="line"><span class="comment">         * finding a non-cancelled one, which will always exist</span></span><br><span class="line"><span class="comment">         * because the head node is never cancelled: A node becomes</span></span><br><span class="line"><span class="comment">         * head only as a result of successful acquire. A</span></span><br><span class="line"><span class="comment">         * cancelled thread never succeeds in acquiring, and a thread only</span></span><br><span class="line"><span class="comment">         * cancels itself, not any other node.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">volatile</span> Node prev;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Link to the successor node that the current node/thread</span></span><br><span class="line"><span class="comment">         * unparks upon release. Assigned during enqueuing, adjusted</span></span><br><span class="line"><span class="comment">         * when bypassing cancelled predecessors, and nulled out (for</span></span><br><span class="line"><span class="comment">         * sake of GC) when dequeued.  The enq operation does not</span></span><br><span class="line"><span class="comment">         * assign next field of a predecessor until after attachment,</span></span><br><span class="line"><span class="comment">         * so seeing a null next field does not necessarily mean that</span></span><br><span class="line"><span class="comment">         * node is at end of queue. However, if a next field appears</span></span><br><span class="line"><span class="comment">         * to be null, we can scan prev&#x27;s from the tail to</span></span><br><span class="line"><span class="comment">         * double-check.  The next field of cancelled nodes is set to</span></span><br><span class="line"><span class="comment">         * point to the node itself instead of null, to make life</span></span><br><span class="line"><span class="comment">         * easier for isOnSyncQueue.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">volatile</span> Node next;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * The thread that enqueued this node.  Initialized on</span></span><br><span class="line"><span class="comment">         * construction and nulled out after use.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">volatile</span> Thread thread;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Link to next node waiting on condition, or the special</span></span><br><span class="line"><span class="comment">         * value SHARED.  Because condition queues are accessed only</span></span><br><span class="line"><span class="comment">         * when holding in exclusive mode, we just need a simple</span></span><br><span class="line"><span class="comment">         * linked queue to hold nodes while they are waiting on</span></span><br><span class="line"><span class="comment">         * conditions. They are then transferred to the queue to</span></span><br><span class="line"><span class="comment">         * re-acquire. And because conditions can only be exclusive,</span></span><br><span class="line"><span class="comment">         * we save a field by using special value to indicate shared</span></span><br><span class="line"><span class="comment">         * mode.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        Node nextWaiter;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Returns true if node is waiting in shared mode.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">isShared</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> nextWaiter == SHARED;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Returns previous node, or throws NullPointerException if null.</span></span><br><span class="line"><span class="comment">         * Use when predecessor cannot be null.  The null check could</span></span><br><span class="line"><span class="comment">         * be elided, but is present to help the VM.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@return</span> the predecessor of this node</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">final</span> Node <span class="title">predecessor</span><span class="params">()</span> <span class="keyword">throws</span> NullPointerException </span>&#123;</span><br><span class="line">            Node p = prev;</span><br><span class="line">            <span class="keyword">if</span> (p == <span class="keyword">null</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">return</span> p;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node() &#123;    <span class="comment">// Used to establish initial head or SHARED marker</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node(Thread thread, Node mode) &#123;     <span class="comment">// Used by addWaiter</span></span><br><span class="line">            <span class="keyword">this</span>.nextWaiter = mode;</span><br><span class="line">            <span class="keyword">this</span>.thread = thread;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node(Thread thread, <span class="keyword">int</span> waitStatus) &#123; <span class="comment">// Used by Condition</span></span><br><span class="line">            <span class="keyword">this</span>.waitStatus = waitStatus;</span><br><span class="line">            <span class="keyword">this</span>.thread = thread;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<p>解释一下几个方法和属性值的含义：</p>
<table>
<thead>
<tr>
<th align="left">方法和属性值</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">waitStatus</td>
<td align="left">当前节点在队列中的状态</td>
</tr>
<tr>
<td align="left">thread</td>
<td align="left">表示处于该节点的线程</td>
</tr>
<tr>
<td align="left">prev</td>
<td align="left">前驱指针</td>
</tr>
<tr>
<td align="left">predecessor</td>
<td align="left">返回前驱节点，没有的话抛出空指针异常</td>
</tr>
<tr>
<td align="left">nextWaiter</td>
<td align="left">指向下一个处于CONDITION状态的节点（由于本篇文章不讲述Condition Queue队列，这个指针不多介绍）</td>
</tr>
<tr>
<td align="left">next</td>
<td align="left">后继指针</td>
</tr>
</tbody></table>
<p>线程两种锁的模式：</p>
<table>
<thead>
<tr>
<th align="left">模式</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">SHARED</td>
<td align="left">表示线程以共享的模式等待锁</td>
</tr>
<tr>
<td align="left">EXCLUSIVE</td>
<td align="left">表示线程正在以独占的方式等待锁</td>
</tr>
</tbody></table>
<p>waitStatus有下面几个枚举值：</p>
<table>
<thead>
<tr>
<th align="left">枚举</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">0</td>
<td align="left">当一个Node被初始化的时候的默认值</td>
</tr>
<tr>
<td align="left">CANCELLED</td>
<td align="left">为1，表示线程获取锁的请求已经取消了</td>
</tr>
<tr>
<td align="left">CONDITION</td>
<td align="left">为-2，表示节点在等待队列中，节点线程等待唤醒</td>
</tr>
<tr>
<td align="left">PROPAGATE</td>
<td align="left">为-3，当前线程处在SHARED情况下，该字段才会使用</td>
</tr>
<tr>
<td align="left">SIGNAL</td>
<td align="left">为-1，表示线程已经准备好了，就等资源释放了</td>
</tr>
</tbody></table>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226114029036.png" alt="image-20211226114029036"></p>
<h3 id="同步状态State"><a href="#同步状态State" class="headerlink" title="同步状态State"></a>同步状态State</h3><p>在了解数据结构后，接下来了解一下AQS的同步状态——State。AQS中维护了一个名为state的字段，意为同步状态，是由Volatile修饰的，用于展示当前临界资源的获锁情况。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br></pre></td></tr></table></figure>

<p>下面提供了几个访问这个字段的方法：</p>
<table>
<thead>
<tr>
<th align="left">方法名</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">protected final int getState()</td>
<td align="left">获取State的值</td>
</tr>
<tr>
<td align="left">protected final void setState(int newState)</td>
<td align="left">设置State的值</td>
</tr>
<tr>
<td align="left">protected final boolean compareAndSetState(int expect, int update)</td>
<td align="left">使用CAS方式更新State</td>
</tr>
</tbody></table>
<p>这几个方法都是Final修饰的，说明子类中无法重写它们。我们可以通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式（加锁过程）。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/27605d483e8935da683a93be015713f331378.png" alt="img"></p>
<p>对于我们自定义的同步工具，需要自定义获取同步状态和释放状态的方式，也就是AQS架构图中的第一层：API层。</p>
<h3 id="AQS重要方法与ReentrantLock的关联"><a href="#AQS重要方法与ReentrantLock的关联" class="headerlink" title="AQS重要方法与ReentrantLock的关联"></a>AQS重要方法与ReentrantLock的关联</h3><p>从架构图中可以得知，AQS提供了大量用于自定义同步器实现的Protected方法。自定义同步器实现的相关方法也只是为了通过修改State字段来实现多线程的独占模式或者共享模式。自定义同步器需要实现以下方法（ReentrantLock需要实现的方法如下，并不是全部）：</p>
<table>
<thead>
<tr>
<th align="left">方法名</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">protected boolean isHeldExclusively()</td>
<td align="left">该线程是否正在独占资源。只有用到Condition才需要去实现它。</td>
</tr>
<tr>
<td align="left">protected boolean tryAcquire(int arg)</td>
<td align="left">独占方式。arg为获取锁的次数，尝试获取资源，成功则返回True，失败则返回False。</td>
</tr>
<tr>
<td align="left">protected boolean tryRelease(int arg)</td>
<td align="left">独占方式。arg为释放锁的次数，尝试释放资源，成功则返回True，失败则返回False。</td>
</tr>
<tr>
<td align="left">protected int tryAcquireShared(int arg)</td>
<td align="left">共享方式。arg为获取锁的次数，尝试获取资源。负数表示失败；0表示成功，但没有剩余可用资源；正数表示成功，且有剩余资源。</td>
</tr>
<tr>
<td align="left">protected boolean tryReleaseShared(int arg)</td>
<td align="left">共享方式。arg为释放锁的次数，尝试释放资源，如果释放后允许唤醒后续等待结点返回True，否则返回False。</td>
</tr>
</tbody></table>
<p>一般来说，自定义同步器要么是独占方式，要么是共享方式，它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式，如ReentrantReadWriteLock。ReentrantLock是独占锁，所以实现了tryAcquire-tryRelease。</p>
<p>以非公平锁为例，这里主要阐述一下非公平锁与AQS之间方法的关联之处，具体每一处核心方法的作用会在文章后面详细进行阐述。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/b8b53a70984668bc68653efe9531573e78636.png" alt="img"></p>
<p>为了帮助大家理解ReentrantLock和AQS之间方法的交互过程，以非公平锁为例，我们将加锁和解锁的交互流程单独拎出来强调一下，以便于对后续内容的理解。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/7aadb272069d871bdee8bf3a218eed8136919.png" alt="img"></p>
<p>加锁：</p>
<ul>
<li>通过ReentrantLock的加锁方法Lock进行加锁操作。</li>
<li>会调用到内部类Sync的Lock方法，由于Sync#lock是抽象方法，根据ReentrantLock初始化选择的公平锁和非公平锁，执行相关内部类的Lock方法，本质上都会执行AQS的Acquire方法。</li>
<li>AQS的Acquire方法会执行tryAcquire方法，但是由于tryAcquire需要自定义同步器实现，因此执行了ReentrantLock中的tryAcquire方法，由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法，因此会根据锁类型不同，执行不同的tryAcquire。</li>
<li>tryAcquire是获取锁逻辑，获取失败后，会执行框架AQS的后续逻辑，跟ReentrantLock自定义同步器无关。</li>
</ul>
<p>解锁：</p>
<ul>
<li>通过ReentrantLock的解锁方法Unlock进行解锁。</li>
<li>Unlock会调用内部类Sync的Release方法，该方法继承于AQS。</li>
<li>Release中会调用tryRelease方法，tryRelease需要自定义同步器实现，tryRelease只在ReentrantLock中的Sync实现，因此可以看出，释放锁的过程，并不区分是否为公平锁。</li>
<li>释放成功后，所有处理由AQS框架完成，与自定义同步器无关。</li>
</ul>
<p>通过上面的描述，大概可以总结出ReentrantLock加锁解锁时API层核心方法的映射关系。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/f30c631c8ebbf820d3e8fcb6eee3c0ef18748.png" alt="img"></p>
<h3 id="通过ReentrantLock理解AQS"><a href="#通过ReentrantLock理解AQS" class="headerlink" title="通过ReentrantLock理解AQS"></a>通过ReentrantLock理解AQS</h3><p>ReentrantLock中公平锁和非公平锁在底层是相同的，这里以非公平锁为例进行分析。</p>
<p>在非公平锁中，有一段这样的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Sync object for non-fair locks</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">NonfairSync</span> <span class="keyword">extends</span> <span class="title">Sync</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">7316153563782823691L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Performs lock.  Try immediate barge, backing up to normal</span></span><br><span class="line"><span class="comment">     * acquire on failure.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> nonfairTryAcquire(acquires);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看一下这个Acquire是怎么写的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp; acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">		selfInterrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Acquire中调用了<code>tryAcquire()</code>方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可以看出，这里只是AQS的简单实现，具体获取锁的实现方法是由各自的公平锁和非公平锁单独实现的（以ReentrantLock为例）。如果该方法返回了True，则说明当前线程获取锁成功，就不用往后执行了；如果获取失败，就需要加入到等待队列中。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226144342912.png" alt="image-20211226144342912"></p>
<p>非公平锁和公平锁的tryAcquire()方法的实现代码，其实差别就在于非公平锁获取锁时比公平锁少了一个判断<code>!hasQueuedPredecessors()</code></p>
<p><code>hasQueuedPredecessors()</code>中判断了是否需要排队，导致公平锁和非公平锁的差异如下：</p>
<p>公平锁：公平锁讲究先来先到，线程在获取锁时，如果这个锁的等待队列中已经有线程在等待，那么当前线程就会进入等待队列中。</p>
<p>非公平锁：不管是否有等待队列，如果可以获取锁，则立刻占有锁对象。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226152202628.png" alt="image-20211226152202628"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> kylin</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AQSDemo</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            lock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;------A thread come in&quot;</span>);</span><br><span class="line">                <span class="comment">//暂停几秒钟线程</span></span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">&quot;A&quot;</span>).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            lock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;------B thread come in&quot;</span>);</span><br><span class="line">                <span class="comment">//暂停几秒钟线程</span></span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">&quot;B&quot;</span>).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            lock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;------C thread come in&quot;</span>);</span><br><span class="line">                <span class="comment">//暂停几秒钟线程</span></span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">&quot;C&quot;</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>假设有三个人去银行办理业务，第一个的A前面没有人，首先办理业务。调用<code>lock()</code>方法</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226152336696.png" alt="image-20211226152336696"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        acquire(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先会使用<code>compareAndSetState(0, 1)</code>方法进行判断</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226152507193.png" alt="image-20211226152507193"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">compareAndSetState</span><span class="params">(<span class="keyword">int</span> expect, <span class="keyword">int</span> update)</span> </span>&#123;</span><br><span class="line">       <span class="comment">// See below for intrinsics setup to support this</span></span><br><span class="line">       <span class="keyword">return</span> unsafe.compareAndSwapInt(<span class="keyword">this</span>, stateOffset, expect, update);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<p>由于目前A是第一个办理业务的，也就是第一个获取锁人。此时state值还是为默认值0，通过CAS操作将其成功更新为<code>1</code>。返回<code>true</code>。进入到If判断中</p>
<p>调用<code>setExclusiveOwnerThread(Thread.currentThread());</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226152912624.png" alt="image-20211226152912624"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Sets the thread that currently owns exclusive access.</span></span><br><span class="line"><span class="comment"> * A &#123;<span class="doctag">@code</span> null&#125; argument indicates that no thread owns access.</span></span><br><span class="line"><span class="comment"> * This method does not otherwise impose any synchronization or</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> volatile&#125; field accesses.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> thread the owner thread</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setExclusiveOwnerThread</span><span class="params">(Thread thread)</span> </span>&#123;</span><br><span class="line">    exclusiveOwnerThread = thread;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>将当前线程设置为独占访问权限的线程，也就是当前线程持有了这把锁。</p>
<p>到此lock方法就结束了，A线程暂停2秒钟。B线程开始运行调用lock方法。</p>
<p>此时<code>state</code>已经被设置为了<code>1</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        acquire(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以不满足判断条件。则运行 <code>acquire(1)</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226155842888.png" alt="image-20211226155842888"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先调用<code>!tryAcquire(1)</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226160105191.png" alt="image-20211226160105191"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> nonfairTryAcquire(acquires);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>非公平锁调用<code>nonfairTryAcquire(1)</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226160421387.png" alt="image-20211226160421387"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">nonfairTryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line">    <span class="keyword">int</span> c = getState();</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(current);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">        <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">        setState(nextc);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>第一个if条件<code>c==0</code>，此时为1不满足</li>
<li>第二个if条件<code>current == getExclusiveOwnerThread()</code>当前访问独占权限的线程是A，不是B。也不满足</li>
<li>所以直接返回false</li>
</ul>
<blockquote>
<p>第一个if条件c == 0什么时候满足呢？</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">             <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">                 setExclusiveOwnerThread(current);</span><br><span class="line">                 <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">             &#125;</span><br><span class="line">         &#125;</span><br></pre></td></tr></table></figure>

<p>其实就是当B线程运行<code>tryAcquire()</code>方法时，A线程刚好完成，释放了锁，将<code>state</code>设置为了<code>0</code>。满足条件，CAS将state设置为<code>1</code>，设置当前访问独占权限的线程为B。返回<code>true</code>。<code>!nonfairTryAcquire(1)</code>为false不满足条件<code>acquire(1)</code>方法结束。<code>lock()</code>成功!</p>
<blockquote>
<p>第二个if条件current == getExclusiveOwnerThread()什么时候满足呢？</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">          <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">          <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">              <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">          setState(nextc);</span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">      &#125;</span><br></pre></td></tr></table></figure>



<p>当此时还是A线程，也就是前访问独占权限的线程尝试获取锁时满足条件。</p>
<p>此时</p>
<ul>
<li><code>nextc = c + acquires</code></li>
<li><code>c=state=1</code></li>
<li><code>acquires=1</code></li>
<li><code>nextc=2</code></li>
<li><code>setState(nextc);</code>设置state为2</li>
</ul>
<p>将state设置为<code>2</code>，返回<code>true</code>。这也就是为什么<code>ReentrantLock</code>为<code>可重入锁</code>。<code>!nonfairTryAcquire(1)</code>为false不满足条件<code>acquire(1)</code>方法结束。<code>lock()</code>成功!</p>
<p>我们接着当前的正常逻辑，回到<code>acquire(1)</code>方法</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226160736984.png" alt="image-20211226160736984"></p>
<p><code>tryAcquire(1)</code>返回<code>false</code>，取否变成<code>true</code>所以接着运行<code>acquireQueued(addWaiter(Node.EXCLUSIVE), 1))</code></p>
<p>首先查看<code>addWaiter(Node.EXCLUSIVE)</code>加入等待队列中</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226170238746.png" alt="image-20211226170238746"></p>
<p><code>static final Node EXCLUSIVE = null;</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226170313810.png" alt="image-20211226170313810"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">addWaiter</span><span class="params">(Node mode)</span> </span>&#123;</span><br><span class="line">    Node node = <span class="keyword">new</span> Node(Thread.currentThread(), mode);</span><br><span class="line">    <span class="comment">// Try the fast path of enq; backup to full enq on failure</span></span><br><span class="line">    Node pred = tail;</span><br><span class="line">    <span class="keyword">if</span> (pred != <span class="keyword">null</span>) &#123;</span><br><span class="line">        node.prev = pred;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetTail(pred, node)) &#123;</span><br><span class="line">            pred.next = node;</span><br><span class="line">            <span class="keyword">return</span> node;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    enq(node);</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226171522045.png" alt="image-20211226171522045"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Node(Thread thread, Node mode) &#123;     <span class="comment">// Used by addWaiter</span></span><br><span class="line">    <span class="keyword">this</span>.nextWaiter = mode;</span><br><span class="line">    <span class="keyword">this</span>.thread = thread;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>主要的流程如下：</p>
<ul>
<li>通过当前的线程和锁模式新建一个节点。</li>
<li>Pred指针指向尾节点Tail。</li>
<li>将New中Node的Prev指针指向Pred。</li>
<li>通过compareAndSetTail方法，完成尾节点的设置。这个方法主要是对tailOffset和Expect进行比较，如果tailOffset的Node和Expect的Node地址是相同的，那么设置Tail的值为Update的值。</li>
</ul>
<p>当前模式为独占模式值<code>mode</code>值为<code>null</code></p>
<blockquote>
<p>如果Pred指针是Null（说明等待队列中没有元素），或者当前Pred指针和Tail指向的位置不同（说明被别的线程已经修改），就需要看一下Enq的方法。</p>
</blockquote>
<p>由于当前等待队列中没有线程进行排队，<code>tail</code>为<code>null</code>所以<code>pred=null</code>不满足if条件，运行<code>enq(node)</code>将节点入队</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">enq</span><span class="params">(<span class="keyword">final</span> Node node)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        Node t = tail;</span><br><span class="line">        <span class="keyword">if</span> (t == <span class="keyword">null</span>) &#123; <span class="comment">// Must initialize</span></span><br><span class="line">            <span class="keyword">if</span> (compareAndSetHead(<span class="keyword">new</span> Node()))</span><br><span class="line">                tail = head;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            node.prev = t;</span><br><span class="line">            <span class="keyword">if</span> (compareAndSetTail(t, node)) &#123;</span><br><span class="line">                t.next = node;</span><br><span class="line">                <span class="keyword">return</span> t;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Node t = tail</code>tail为<code>null</code>,满足if条件，调用<code>compareAndSetHead(new Node())</code>CAS设置队列头节点。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211226172258714.png" alt="image-20211226172258714"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">compareAndSetHead</span><span class="params">(Node update)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//当期望值为null，也就是只有头节点为null时才能设置成功</span></span><br><span class="line">    <span class="keyword">return</span> unsafe.compareAndSwapObject(<span class="keyword">this</span>, headOffset, <span class="keyword">null</span>, update);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>如果没有被初始化，需要进行初始化一个头结点出来。但请注意，初始化的头结点并不是当前线程节点，而是调用了无参构造函数的节点。如果经历了初始化或者并发导致队列中有元素，则与之前的方法相同。其实，addWaiter就是一个在双端链表添加尾节点的操作，需要注意的是，双端链表的头结点是一个无参构造函数的头结点。</p>
</blockquote>
<p>此时头节点head为<code>null</code>，创建一个新节点Node设置为头节点，成功返回<code>true</code>将<code>tail = head;</code>头节点赋值给尾节点。第一个节点头节点，是创建的哨兵节点（虚拟头节点）。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="D:%5CFiles%5CBlog_Images%5CAQS%E6%8A%BD%E8%B1%A1%E7%9A%84%E9%98%9F%E5%88%97%E5%90%8C%E6%AD%A5%E5%99%A8%5Cimage-20211227215702607.png" alt="image-20211227215702607"></p>
<p>由于<code>for (;;)</code>所以接着循环此时<code>t==null</code>不成立，<code>node.prev = t;</code>将B的前驱指向尾节点（此时尾节点）。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211227220207554.png" alt="image-20211227220207554"></p>
<p><code>compareAndSetTail(t, node)</code>接着通过CAS操作将尾节点设置为了<code>node</code>也就是B。接着将<code>t.next=node</code>将其后继结点指向node即B。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211227221619148.png" alt="image-20211227221619148"></p>
<p>然后结束循环，回到<code>addWaiter()</code>,运行<code>return node</code>也就是将B节点返回出去。此时回到<code>acquire(1)</code>中，执行<code>acquireQueued(B,1))</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229213344336.png" alt="image-20211229213344336"></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229213503044.png" alt="image-20211229213503044"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="keyword">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                interrupted = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="keyword">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                interrupted = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>进入循环<code>final Node p = node.predecessor();</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns previous node, or throws NullPointerException if null.</span></span><br><span class="line"><span class="comment"> * Use when predecessor cannot be null.  The null check could</span></span><br><span class="line"><span class="comment"> * be elided, but is present to help the VM.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the predecessor of this node</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> Node <span class="title">predecessor</span><span class="params">()</span> <span class="keyword">throws</span> NullPointerException </span>&#123;</span><br><span class="line">    Node p = prev;</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>返回当前节点的前节点，也就是B的前节点，即<code>head</code>头节点。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">             setHead(node);</span><br><span class="line">             p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">             failed = <span class="keyword">false</span>;</span><br><span class="line">             <span class="keyword">return</span> interrupted;</span><br><span class="line">         &#125;</span><br></pre></td></tr></table></figure>

<p>此时<code>p==head</code>满足，再次<code>tryAcquire(arg)</code>尝试获取锁，获取成功的话</p>
<p>则<code>setHead(node);</code>将B设置为新的头节点。而以前的头节点<code>p.next</code></p>
<p>将其后节点指针设置为null（便于GC）返回<code>interrupted</code>false。结束。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setHead</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    head = node;</span><br><span class="line">    node.thread = <span class="keyword">null</span>;</span><br><span class="line">    node.prev = <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果不满足<code>p == head &amp;&amp; tryAcquire(arg)</code>则进入下一个判断</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">    <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">        parkAndCheckInterrupt())</span><br><span class="line">        interrupted = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>shouldParkAfterFailedAcquire(p, node)</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229220356776.png" alt="image-20211229220356776"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">shouldParkAfterFailedAcquire</span><span class="params">(Node pred, Node node)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ws = pred.waitStatus;</span><br><span class="line">    <span class="keyword">if</span> (ws == Node.SIGNAL)</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * This node has already set status asking a release</span></span><br><span class="line"><span class="comment">         * to signal it, so it can safely park.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">if</span> (ws &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * Predecessor was cancelled. Skip over predecessors and</span></span><br><span class="line"><span class="comment">         * indicate retry.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            node.prev = pred = pred.prev;</span><br><span class="line">        &#125; <span class="keyword">while</span> (pred.waitStatus &gt; <span class="number">0</span>);</span><br><span class="line">        pred.next = node;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * waitStatus must be 0 or PROPAGATE.  Indicate that we</span></span><br><span class="line"><span class="comment">         * need a signal, but don&#x27;t park yet.  Caller will need to</span></span><br><span class="line"><span class="comment">         * retry to make sure it cannot acquire before parking.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>int ws = pred.waitStatus;</code>此时pred代表头节点，ws值为初始值<code>0</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">           <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * waitStatus must be 0 or PROPAGATE.  Indicate that we</span></span><br><span class="line"><span class="comment">            * need a signal, but don&#x27;t park yet.  Caller will need to</span></span><br><span class="line"><span class="comment">            * retry to make sure it cannot acquire before parking.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">           compareAndSetWaitStatus(pred, ws, Node.SIGNAL);</span><br><span class="line">       &#125;</span><br></pre></td></tr></table></figure>

<p><code>compareAndSetWaitStatus(pred, ws, Node.SIGNAL);</code>此时将前节点pred也就是头节点的<code>waitStatus</code>值CAS操作成了<code>Node.SIGNAL</code>也就是<code>-1</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229220755241.png" alt="image-20211229220755241"></p>
<p>接着返回<code>false</code>。回到<code>acquireQueued()</code>方法，并没有结束循环，再次循环运行</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229221036238.png" alt="image-20211229221036238"></p>
<p>接着再度运行<code>shouldParkAfterFailedAcquire(p, node)</code>，此时</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229221210712.png" alt="image-20211229221210712"></p>
<p>头节点<code>pred</code>的<code>waitStatus== Node.SIGNAL</code>进入<code>return true</code></p>
<p>就能运行<code>parkAndCheckInterrupt())</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229221346536.png" alt="image-20211229221346536"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Convenience method to park and then check if interrupted</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; if interrupted</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">return</span> Thread.interrupted();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>调用改方法，运行<code>LockSupport.park(this);</code>暂停该线程。返回<code>Thread.interrupted();</code>，线程暂停成功则返回<code>true</code>。线程B暂停，等待唤醒…….(方法并没有结束，只是暂停了，唤醒之后继续运行)</p>
<p>此时A线程终于<code>unlock()</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229222141983.png" alt="image-20211229222141983"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">     sync.release(<span class="number">1</span>);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229222313708.png" alt="image-20211229222313708"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (tryRelease(arg)) &#123;</span><br><span class="line">        Node h = head;</span><br><span class="line">        <span class="keyword">if</span> (h != <span class="keyword">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">            unparkSuccessor(h);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>进入<code>tryRelease(1)</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229222400872.png" alt="image-20211229222400872"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> releases)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> c = getState() - releases;</span><br><span class="line">    <span class="keyword">if</span> (Thread.currentThread() != getExclusiveOwnerThread())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line">    <span class="keyword">boolean</span> free = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        free = <span class="keyword">true</span>;</span><br><span class="line">        setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    setState(c);</span><br><span class="line">    <span class="keyword">return</span> free;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>此时<code>getState()</code>state的值为<code>1</code></p>
<p><code>int c = getState() - releases;</code>等于1-1为<code>0</code></p>
<p><code>if (Thread.currentThread() != getExclusiveOwnerThread())</code></p>
<p>如果当前解锁线程不是锁的持有者，则抛出<code>IllegalMonitorStateException</code>异常。</p>
<p>如果<code>c==0</code>则进判断</p>
<ul>
<li>设置<code>free=true</code></li>
<li><code>setExclusiveOwnerThread(null);</code>独占线程（锁拥有者）为null</li>
<li><code>setState(c);</code>变为0</li>
<li>返回free，也就是true</li>
</ul>
<p>如果<code>c!=0</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229222934762.png" alt="image-20211229222934762"></p>
<p>在<code>nonfairTryAcquire()</code>中嵌入式锁中给该值进行过，加值操作。此时只释放了一次，假设进行了两次<code>lock</code>,则设置新的state值，也就是<code>1</code>,返回<code>false</code>。结束。锁还没有释放，需要该线程再度<code>unlock()</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229224017091.png" alt="image-20211229224017091"></p>
<p>锁的嵌入式情况，此时lockMethod2()的unlock</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211229224137285.png" alt="image-20211229224137285"></p>
<p>解锁了一次，之后state变成了1，还需要lockMethod2()的unlock，才能真正释放锁。</p>
<p>回到主流程，A线程unlock释放锁，<code>tryRelease</code>成功返回true，state变成了0。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (tryRelease(arg)) &#123;</span><br><span class="line">    Node h = head;</span><br><span class="line">    <span class="keyword">if</span> (h != <span class="keyword">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">        unparkSuccessor(h);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>此时<code>h != null &amp;&amp; h.waitStatus != 0</code></p>
<ul>
<li>头节点h不等于null，为哨兵节点。</li>
<li>waitStatus在前面B节点等待的时候使用<code>compareAndSetWaitStatus()</code>变成了<code>-1</code></li>
</ul>
<p>进入判断调用<code>unparkSuccessor(h);</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20220101202314582.png" alt="image-20220101202314582"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Wakes up node&#x27;s successor, if one exists.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> node the node</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">unparkSuccessor</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * If status is negative (i.e., possibly needing signal) try</span></span><br><span class="line"><span class="comment">     * to clear in anticipation of signalling.  It is OK if this</span></span><br><span class="line"><span class="comment">     * fails or if status is changed by waiting thread.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">int</span> ws = node.waitStatus;</span><br><span class="line">    <span class="keyword">if</span> (ws &lt; <span class="number">0</span>)</span><br><span class="line">        compareAndSetWaitStatus(node, ws, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Thread to unpark is held in successor, which is normally</span></span><br><span class="line"><span class="comment">     * just the next node.  But if cancelled or apparently null,</span></span><br><span class="line"><span class="comment">     * traverse backwards from tail to find the actual</span></span><br><span class="line"><span class="comment">     * non-cancelled successor.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Node s = node.next;</span><br><span class="line">    <span class="keyword">if</span> (s == <span class="keyword">null</span> || s.waitStatus &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        s = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (Node t = tail; t != <span class="keyword">null</span> &amp;&amp; t != node; t = t.prev)</span><br><span class="line">            <span class="keyword">if</span> (t.waitStatus &lt;= <span class="number">0</span>)</span><br><span class="line">                s = t;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (s != <span class="keyword">null</span>)</span><br><span class="line">        LockSupport.unpark(s.thread);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>int ws = node.waitStatus;</code>头节点的waitStatus为<code>-1</code>小于0，调用</p>
<p><code>compareAndSetWaitStatus(node, ws, 0);</code>将其又变成了<code>0</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20211227221619148.png" alt="image-20211227221619148"></p>
<p>接着<code>Node s = node.next</code>头节点下一个节点也就是B，<code>if (s == null || s.waitStatus &gt; 0)</code></p>
<ul>
<li>B节点不等于null</li>
<li>B节点的waitStatus等于0，不满足大于0</li>
</ul>
<p><code>if (s != null)</code>满足后，调用<code>LockSupport.unpark(s.thread);</code>此时A的<code>unlock()</code>也就执行完成了。B线程被唤醒。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20220101203436838.png" alt="image-20220101203436838"></p>
<p>此时B线程被唤醒继续执行代码将<code>interrupted = true;</code>设置为true，接着继续遍历循环，<code>final Node p = node.predecessor();</code>B的前驱节点p为head，执行<code>tryAcquire(arg)</code>此时由于没用了A线程持有锁，B线程获取到了锁，满足条件，调用<code>setHead(node)</code>方法，将B节点的属性赋值给头节点（B此时也就是new Node()）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setHead</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    head = node;</span><br><span class="line">    node.thread = <span class="keyword">null</span>;</span><br><span class="line">    node.prev = <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其实也就是搞了一个新的哨兵节点(watiStatus=0,thread=0,next则没用改变，如果还有C的话，还是指向C的)，将原先指向头节点的前指针为null,</p>
<p>接着<code>p.next = null;</code>原先的头节点再也没用任何指针指向，会被GC掉。</p>
<p><code>failed=false</code>返回interrupted也就是<code>ture</code></p>
<p>接着<code>if (!tryAcquire(arg) &amp;&amp;
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</code>满足判断</p>
<p>调用<code>selfInterrupt();</code></p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20220101210117318.png" alt="image-20220101210117318"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">selfInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Thread.currentThread().interrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20220101210210755.png" alt="image-20220101210210755"></p>
<p>其实B线程唤醒后，应该是从这里再继续执行。</p>
<p><img src= "https://gitee.com/kylincw/images/raw/master/loading.gif" data-lazy-src="https://qiniu.codekylin.cn/img/image-20220101205936259.png" alt="image-20220101205936259"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">park</span><span class="params">(Object blocker)</span> </span>&#123;</span><br><span class="line">    Thread t = Thread.currentThread();</span><br><span class="line">    setBlocker(t, blocker);</span><br><span class="line">    UNSAFE.park(<span class="keyword">false</span>, <span class="number">0L</span>);</span><br><span class="line">    setBlocker(t, <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>setBlocker(t, null);</code>设置blocket为<code>null</code></p>
<p>所以<code>Interruptible b = blocker;</code> b为null不满足条件，没有中断线程B，只是<code>interrupt0();</code>设置 <code>interrupt flag</code>为false。到此结束。</p>
<blockquote>
<p>但为什么获取了锁以后还要中断线程呢？这部分属于Java提供的协作式中断知识内容，感兴趣同学可以查阅一下。这里简单介绍一下：</p>
</blockquote>
<ol>
<li>当中断线程被唤醒时，并不知道被唤醒的原因，可能是当前线程在等待中被中断，也可能是释放了锁以后被唤醒。因此我们通过Thread.interrupted()方法检查中断标记（该方法返回了当前线程的中断状态，并将当前线程的中断标识设置为False），并记录下来，如果发现该线程被中断过，就再中断一次。</li>
<li>线程在等待资源的过程中被唤醒，唤醒后还是会不断地去尝试获取锁，直到抢到锁为止。也就是说，在整个流程中，并不响应中断，只是记录中断记录。最后抢到锁返回了，那么如果被中断过的话，就需要补充一次中断。</li>
</ol>
<p>这里的处理方式主要是运用线程池中基本运作单元Worder中的runWorker，通过Thread.interrupted()进行额外的判断处理，感兴趣的可以看下ThreadPoolExecutor源码。</p>
<h3 id="JUC中的应用场景"><a href="#JUC中的应用场景" class="headerlink" title="JUC中的应用场景"></a>JUC中的应用场景</h3><p>除了上边ReentrantLock的可重入性的应用，AQS作为并发编程的框架，为很多其他同步工具提供了良好的解决方案。下面列出了JUC中的几种同步工具，大体介绍一下AQS的应用场景：</p>
<table>
<thead>
<tr>
<th align="left">同步工具</th>
<th align="left">同步工具与AQS的关联</th>
</tr>
</thead>
<tbody><tr>
<td align="left">ReentrantLock</td>
<td align="left">使用AQS保存锁重复持有的次数。当一个线程获取锁时，ReentrantLock记录当前获得锁的线程标识，用于检测是否重复获取，以及错误线程试图解锁操作时异常情况的处理。</td>
</tr>
<tr>
<td align="left">Semaphore</td>
<td align="left">使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数，acquireShared会减少计数。</td>
</tr>
<tr>
<td align="left">CountDownLatch</td>
<td align="left">使用AQS同步状态来表示计数。计数为0时，所有的Acquire操作（CountDownLatch的await方法）才可以通过。</td>
</tr>
<tr>
<td align="left">ReentrantReadWriteLock</td>
<td align="left">使用AQS同步状态中的16位保存写锁持有的次数，剩下的16位用于保存读锁的持有次数。</td>
</tr>
<tr>
<td align="left">ThreadPoolExecutor</td>
<td align="left">Worker利用AQS同步状态实现对独占线程变量的设置（tryAcquire和tryRelease）。</td>
</tr>
</tbody></table>
<h3 id="自定义同步工具"><a href="#自定义同步工具" class="headerlink" title="自定义同步工具"></a>自定义同步工具</h3><p>了解AQS基本原理以后，按照上面所说的AQS知识点，自己实现一个同步工具。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">Kylin</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://www.codekylin.cn/59732.html">https://www.codekylin.cn/59732.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://www.codekylin.cn" target="_blank">Kylin</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/JUC/">JUC</a><a class="post-meta__tags" href="/tags/AQS/">AQS</a></div><div class="post_share"><div class="social-share" data-image="https://qiniu.codekylin.cn/github/img/img/博客封面14.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><div class="post-reward"><div class="reward-button button--animated"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="https://qiniu.codekylin.cn/img/20200807181442.jpg" target="_blank"><img class="post-qr-code-img" data-lazy-src="https://qiniu.codekylin.cn/img/20200807181442.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="https://qiniu.codekylin.cn/img/20200807181505.jpg" target="_blank"><img class="post-qr-code-img" data-lazy-src="https://qiniu.codekylin.cn/img/20200807181505.jpg" alt="支付寶"/></a><div class="post-qr-code-desc">支付寶</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/64631.html"><img class="prev-cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-5wqmj8.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Redis分布式锁</div></div></a></div><div class="next-post pull-right"><a href="/20208.html"><img class="next-cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-1j66e3.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">LockSupport详解</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span> 相关推荐</span></div><div class="relatedPosts-list"><div><a href="/60468.html" title="JUC之Callable接口"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-0pvrpm.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-27</div><div class="title">JUC之Callable接口</div></div></a></div><div><a href="/28239.html" title="JUC之synchronized和Lock"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-od5gj7.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-24</div><div class="title">JUC之synchronized和Lock</div></div></a></div><div><a href="/47426.html" title="JUC之公平锁和非公平锁"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-3kp6yv.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-26</div><div class="title">JUC之公平锁和非公平锁</div></div></a></div><div><a href="/23872.html" title="JUC之八锁问题"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-mdz9x9.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-24</div><div class="title">JUC之八锁问题</div></div></a></div><div><a href="/51393.html" title="JUC之可重入锁"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/images/20200601091329.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-26</div><div class="title">JUC之可重入锁</div></div></a></div><div><a href="/1215.html" title="JUC之强大的辅助类"><img class="cover" data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-nzj88g.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-09-26</div><div class="title">JUC之强大的辅助类</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="card-info-avatar is-center"><img class="avatar-img" data-lazy-src="https://qiniu.codekylin.cn/img/20200807181526.jpg" onerror="this.onerror=null;this.src='https://qiniu.codekylin.cn/github/img/friend_404.gif'" alt="avatar"/><div class="author-info__name">Kylin</div><div class="author-info__description">学习不易，努力努力~</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">362</div></a></div><div class="card-info-data-item is-center"><a href="/tags/"><div class="headline">标签</div><div class="length-num">427</div></a></div><div class="card-info-data-item is-center"><a href="/categories/"><div class="headline">分类</div><div class="length-num">101</div></a></div></div><a class="button--animated" id="card-info-btn"><i class="fas fa-bookmark"></i><span>加入书签</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/kylincw" target="_blank" title="Github"><i class="iconfont icon-github"></i></a><a class="social-icon" href="tencent://message/?Menu=yes&amp;uin=171346168&amp;Service=300&amp;sigT=45a1e5847943b64c6ff3990f8a9e644d2b31356cb0b4ac6b24663a3c8dd0f8aa12a595b1714f9d45" target="_blank" title="qq"><i class="iconfont icon-qq"></i></a><a class="social-icon" href="https://space.bilibili.com/53836035" target="_blank" title="BiliBili"><i class="iconfont icon-bilibili-line"></i></a><a class="social-icon" href="mailto:zhang171346168@qq.com" target="_blank" title="Email"><i class="iconfont icon-email1"></i></a><a class="social-icon" href="/atom.xml" target="_blank" title="RSS"><i class="iconfont icon-rss"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">学习不易，努力努力！</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A6%82%E5%BF%B5"><span class="toc-number">1.</span> <span class="toc-text">概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A1%86%E6%9E%B6"><span class="toc-number">2.</span> <span class="toc-text">框架</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84"><span class="toc-number">3.</span> <span class="toc-text">数据结构</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%8C%E6%AD%A5%E7%8A%B6%E6%80%81State"><span class="toc-number">4.</span> <span class="toc-text">同步状态State</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#AQS%E9%87%8D%E8%A6%81%E6%96%B9%E6%B3%95%E4%B8%8EReentrantLock%E7%9A%84%E5%85%B3%E8%81%94"><span class="toc-number">5.</span> <span class="toc-text">AQS重要方法与ReentrantLock的关联</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%80%9A%E8%BF%87ReentrantLock%E7%90%86%E8%A7%A3AQS"><span class="toc-number">6.</span> <span class="toc-text">通过ReentrantLock理解AQS</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#JUC%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="toc-number">7.</span> <span class="toc-text">JUC中的应用场景</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7"><span class="toc-number">8.</span> <span class="toc-text">自定义同步工具</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/56352.html" title="be动词"><img data-lazy-src="https://qiniu.codekylin.cn/github/img/img/博客封面10.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="be动词"/></a><div class="content"><a class="title" href="/56352.html" title="be动词">be动词</a><time datetime="2022-07-12T11:47:29.800Z" title="更新于 2022-07-12 19:47:29">2022-07-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/36436.html" title="JVM堆内存"><img data-lazy-src="https://qiniu.codekylin.cn/img/20200418115059.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JVM堆内存"/></a><div class="content"><a class="title" href="/36436.html" title="JVM堆内存">JVM堆内存</a><time datetime="2022-07-12T11:47:29.800Z" title="更新于 2022-07-12 19:47:29">2022-07-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/44292.html" title="Java多线程详解"><img data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-eorjzk.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Java多线程详解"/></a><div class="content"><a class="title" href="/44292.html" title="Java多线程详解">Java多线程详解</a><time datetime="2022-07-12T11:47:29.800Z" title="更新于 2022-07-12 19:47:29">2022-07-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/40200.html" title="谷粒商城记录"><img data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-6qvvrx.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="谷粒商城记录"/></a><div class="content"><a class="title" href="/40200.html" title="谷粒商城记录">谷粒商城记录</a><time datetime="2022-07-12T11:47:29.800Z" title="更新于 2022-07-12 19:47:29">2022-07-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/27082.html" title="Spring学习-3"><img data-lazy-src="https://qiniu.codekylin.cn/github/img/img/wallhaven-4x28xo.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Spring学习-3"/></a><div class="content"><a class="title" href="/27082.html" title="Spring学习-3">Spring学习-3</a><time datetime="2022-07-12T11:47:29.799Z" title="更新于 2022-07-12 19:47:29">2022-07-12</time></div></div></div></div></div></div></main><footer id="footer" style="background-image: url('https://qiniu.codekylin.cn/github/img/img/博客封面14.png')"><div id="footer-wrap"><div class="copyright">&copy;2019 - 2022 By Kylin</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/"><img class="icp-icon" src="https://img.alicdn.com/tfs/TB1..50QpXXXXX7XpXXXXXXXXXX-40-40.png"><span>湘ICP备2022005420号-1</span></a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="font-plus" type="button" title="放大字体"><i class="fas fa-plus"></i></button><button id="font-minus" type="button" title="缩小字体"><i class="fas fa-minus"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><div class="search-dialog__title" id="local-search-title">本地搜索</div><div id="local-input-panel"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div></div><hr/><div id="local-search-results"></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 'ClIyIUhj1ue2rcRTsApYCR50-gzGzoHsz',
      appKey: 'ATug9IScYQBHILhKWEqBHYxM',
      placeholder: '昵称填入QQ号能获取到QQ头像哦~请输入正确的邮箱地址，你将会快速收到我的回复并且通过邮件通知你！~',
      avatar: 'robohash',
      meta: 'nick,mail,link'.split(','),
      pageSize: '10',
      lang: 'zh-cn',
      recordIP: false,
      serverURLs: 'https://cliyiuhj.lc-cn-n1-shared.com',
      emojiCDN: '',
      emojiMaps: "",
      enableQQ: true,
      path: window.location.pathname,
      requiredFields: ["nick,mail"],
      visitor: false
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script defer="defer" id="fluttering_ribbon" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-fluttering-ribbon.min.js"></script></div></body></html>